package com.isyscore.os.driver;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.isyscore.os.driver.core.driver.*;
import com.isyscore.os.driver.core.driver.TslEventAction;
import com.isyscore.os.driver.core.enums.AttributeOperationEnum;
import com.isyscore.os.driver.core.iedge.IEdgeDeviceDriverBase;

import com.isyscore.os.driver.utils.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.isyscore.os.driver.AESUtils.*;
import static com.isyscore.os.driver.AESUtils.parseByte2HexStr;

public class LFParkAccess extends IEdgeDeviceDriverBase {
    private static final Logger log = LoggerFactory.getLogger(LFParkAccess.class);
    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final Map<AsciiString,String> staticHeadersMap = new HashMap<AsciiString,String>() {{
        put(HttpHeaderNames.CONTENT_TYPE, "application/json");
        put(HttpHeaderNames.CONNECTION, "keep-alive");
    }};
    HttpServer httpServer = null;
    private static class Device {
        private Boolean toOnline = false;
        private Boolean online = false;
        private String devId;
        private String devName;
        private int channel;
        private int type;
    }
    private String path = null;
    private String userAccount = null;
    private String userPassword = null;
    private String aesSecret = null;
    private String userId = "";
    private Key aesKey = null;
    int exportPort = 9989;
    String stationNo;
    private HttpClient httpClient;
    private Map<String, Device> deviceIdMap = new HashMap<>();
    private Map<Integer, Device> channelMap = new HashMap<>();
    private Map<Integer, Device> parkZoneMap = new HashMap<>();
    private TimerSchedules timerSchedules = new TimerSchedules(1000);

    /**
     * 定义驱动基础参数
     */
    public LFParkAccess() {
        super("bf786278-add8-4eea-92e7-91cdd26194a2", false, false, false);
    }

    @Override
    public DriverFunctions initDriverFunctions() {
        return new DriverFunctions(false, null, null, null);
    }

    @Override
    public List<ConnectParam> initDriverConnectParams() {
        return Arrays.asList(
                new ConnectParam("接口地址", "App Http Server Path"),
                new ConnectParam("账号", "UserAccount"),
                new ConnectParam("密码", "UserPassword"),
                new ConnectParam("密钥", "AesSecret"),
                new ConnectParam("端口", "exportPort"),
                new ConnectParam("终端号", "stationNo")
        );
    }

    private int initialParamHandle(DriverInitialParam initialParam) {
        for (ConnectParam connectParam : initialParam.getConnectParams()) {
            if (connectParam.getValue() == null) {
                log.error("connectParam.getValue() == null");
                continue;
            }
            if (connectParam.getName().equals("接口地址")) {
                path = connectParam.getValue();
            }
            if (connectParam.getName().equals("账号")) {
                userAccount = connectParam.getValue();
            }
            if (connectParam.getName().equals("密码")) {
                userPassword = connectParam.getValue();
            }
            if (connectParam.getName().equals("密钥")) {
                aesSecret = connectParam.getValue();
            }
            if (connectParam.getName().equals("端口")) {
                exportPort = Integer.valueOf(connectParam.getValue());
            }
            if (connectParam.getName().equals("终端号")) {
                stationNo = connectParam.getValue();
            }
        }

        if (path != null && userAccount != null && userPassword != null && aesSecret != null) {
            try {
                httpClient = new HttpClient(path);
            } catch (MalformedURLException e) {
                log.error(e.getMessage(),e.fillInStackTrace());
                return -1;
            }
        } else {
            return -1;
        }

        initialParam.getDeviceInfoList().forEach((deviceInfo)->{

            if (!deviceInfo.getExtra().isEmpty()) {
                String[] extra = deviceInfo.getExtra().split("\\|");
                if (extra.length == 2) {
                    Device device = new Device();
                    device.toOnline = false;
                    device.online = false;
                    device.devName = deviceInfo.getDevName();
                    device.devId = deviceInfo.getDevId();
                    device.type = Integer.valueOf(extra[0]);
                    device.channel = Integer.valueOf(extra[1]);

                    switch (device.type) {
                        case 0:
                            parkZoneMap.put(device.channel,device);
                            break;
                        case 1:
                            channelMap.put(device.channel,device);
                            break;
                        default:
                            break;
                    }
                    deviceIdMap.put(device.devId,device);
                } else {
                    log.error("device {} extra not valid",deviceInfo.getDevId());
                }
            } else {
                log.error("device {} extra not valid",deviceInfo.getDevId());
            }
        });
        return 0;
    }
    private String aesDecode(ByteBuf response) {
        String responseStr = null;
        try {
            byte[] decryptData = decrypt(parseHexStr2Byte(response.toString(StandardCharsets.UTF_8)), aesKey);
            responseStr = new String(decryptData, "utf-8");
        } catch (Exception e) {
            log.error(e.getMessage(),e.fillInStackTrace());
        }
        return responseStr;
    }

    @Override
    public int init(DriverInitialParam initialParam) {
        if (initialParam != null) {
            initialParamHandle(initialParam);
        }
        aesKey = toKey(Base64.decodeBase64(aesSecret));
        BootStrapManager.init(1,10);
        timerSchedules.register(new TimerHandle((timerHandle)->{
            try {
                byte[] encryptData = encrypt(("{\"userAccount\": \"" + userAccount + "\",\"userPassword\": \"" + parseByte2HexStr(DigestUtils.md5(userPassword)) + "\"}").getBytes("utf-8"), aesKey);
                DefHttpRequest httpRequest = new DefHttpRequest(HttpMethod.POST, "/Login", staticHeadersMap, parseByte2HexStr(encryptData));
                httpClient.exec(httpRequest, ((ctx, response) -> {
                    if (response.status().equals(HttpResponseStatus.OK)) {
                        String responseStr = aesDecode(response.content());
                        if (responseStr != null) {
                            JSONObject root = JSONObject.parseObject(responseStr);
                            userId = root.getString("userID");
                            log.info("登陆成功");
                        }
                    }
                }), true);
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
        }),1,0);

        timerSchedules.register(new TimerHandle((timerHandle)->{
            getParkingLotInfo();
            getChannelInfo();
        }),1000,10000);


        httpServer = new HttpServer();
        httpServer.registerFunction("/AddInOrOutRecords",HttpMethod.POST,((ctx, msg) -> {
            JSONObject root = JSONObject.parseObject(aesDecode(msg.content()));
            if (root != null) {
                try {
                    int channelID = Integer.valueOf(root.getString("channelID"));
                    Device device = channelMap.get(channelID);
                    if (device != null) {
                        TslEventAction action = new TslEventAction();
                        action.setDevId(device.devId);
                        Map<String,Object> values = new HashMap<>();
                        int inOrOut = Integer.valueOf(root.getString("inOrOut"));
                        values.put("carCode",root.getString("carCode"));
                        values.put("inTime",simpleDateFormat.parse(root.getString("inTime")).getTime());
                        values.put("guid",root.getString("GUID") == null ? "" : root.getString("GUID"));
                        values.put("devName",device.devName);
                        if (inOrOut == 1) {
                            values.put("outTime",simpleDateFormat.parse(root.getString("passTime")).getTime());
                            action.setIdentifier("vehicleExit");
                        } else {
                            action.setIdentifier("vehicleEntry");
                        }
                        action.setOutputs(values);
                        listener.onEvent(action);
                    }
                } catch (ParseException e) {
                    log.error(e.getMessage(),e);
                }
            }
            String responseBody = "";
            try {
                byte[] encryptData = encrypt("{\"resCode\": 0,\"resMsg\":\"成功\"}".getBytes("utf-8"), aesKey);
                responseBody = parseByte2HexStr(encryptData);
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer(responseBody, CharsetUtil.UTF_8));
            return response;
        }));

        httpServer.registerFunction("/AddChargeRecords",HttpMethod.POST,((ctx, msg) -> {
//            log.info("AddChargeRecords: ");
//            log.info(aesDecode(msg.content()));
            String responseBody = "";
            try {
                byte[] encryptData = encrypt("{\"resCode\": 0,\"resMsg\":\"成功\"}".getBytes("utf-8"), aesKey);
                responseBody = parseByte2HexStr(encryptData);
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer(responseBody, CharsetUtil.UTF_8));
            return response;
        }));

        httpServer.registerFunction("/AddTriggerRecords",HttpMethod.POST,((ctx, msg) -> {
//            log.info("AddTriggerRecords: ");
//            log.info(aesDecode(msg.content()));
            String responseBody = "";
            try {
                byte[] encryptData = encrypt("{\"resCode\": 0,\"resMsg\":\"成功\"}".getBytes("utf-8"), aesKey);
                responseBody = parseByte2HexStr(encryptData);
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer(responseBody, CharsetUtil.UTF_8));
            return response;
        }));

        try {
            httpServer.start(exportPort);
        } catch (InterruptedException e) {
            log.error(e.getMessage(),e);
        }
        timerSchedules.start();
        return 0;
    }

    @Override
    public int exit() {
        timerSchedules.stop();
        try {
            httpServer.close();
        } catch (InterruptedException e) {
            log.error(e.getMessage(),e);
        }
        httpServer = null;
        BootStrapManager.exit();
        return 0;
    }

    private void ctrlParkChannel(Device device,String value) {
        try {
            byte[] encryptData = encrypt(("{\"channelID\": " + device.channel + ",\"controlType\": " + value + ",\"reason\": \"远程控制\",\"userID\": \"" + userId + "\"}").getBytes("utf-8"), aesKey);
            DefHttpRequest httpRequest = new DefHttpRequest(HttpMethod.POST, "/GetGateControlInfo", staticHeadersMap, parseByte2HexStr(encryptData));
            httpClient.exec(httpRequest, ((ctx, response) -> {
                if (response.status().equals(HttpResponseStatus.OK)) {
                    log.info("ctrlParkChannel Response: {}",aesDecode(response.content()));
                }
            }), true);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
    }
    private void getParkingLotInfo() {
        try {
            byte[] encryptData = encrypt(("{}").getBytes("utf-8"), aesKey);
            DefHttpRequest httpRequest = new DefHttpRequest(HttpMethod.POST, "/GetParkingLotInfo", staticHeadersMap, parseByte2HexStr(encryptData));
            httpClient.exec(httpRequest, ((ctx, response) -> {
                if (response.status().equals(HttpResponseStatus.OK)) {
                    String responseStr = aesDecode(response.content());
                    JSONObject root = JSONObject.parseObject(responseStr);
                    JSONArray parkingLots = root.getJSONArray("parkingLotInfo");
                    String chargeRuleDesc = root.getString("chargeRuleDesc");
                    parkZoneMap.forEach((i,device)-> {
                        device.toOnline = false;
                    });

                    for (int i = 0;i < parkingLots.size();i++) {
                        JSONObject parkingLot = parkingLots.getJSONObject(i);
                        String parkingLotName = parkingLot.getString("parkingLotName");
                        int parkingLotId = parkingLot.getIntValue("parkingLotId");
                        int totalNum = parkingLot.getIntValue("totalNum");
                        int totalStopNum = parkingLot.getIntValue("totalStopNum");
                        int totalRemainNum = parkingLot.getIntValue("totalRemainNum");

                        Device device = parkZoneMap.get(parkingLotId);
                        if (device != null) {
                            TslEventAction action = new TslEventAction();
                            action.setDevId(device.devId);
                            Map<String,Object> values = new HashMap<>();
                            values.put("name",parkingLotName);
                            values.put("totalNum",totalNum);
                            values.put("totalStopNum",totalStopNum);
                            values.put("totalRemainNum",totalRemainNum);
                            values.put("chargeRuleDesc",chargeRuleDesc);
                            action.setOutputs(values);
                            listener.onEvent(action);
                            device.toOnline = true;
                        }
                    }
                    parkZoneMap.forEach((i,device)-> {
                        if (!device.online.equals(device.toOnline)) {
                            updateDeviceStatus(device,device.toOnline);
                        }
                    });

                }
            }), true);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
    }
    private void getChannelInfo() {
        try {
            byte[] encryptData = encrypt(("{\"stationNo\": \"" + stationNo + "\"}").getBytes("utf-8"), aesKey);
            DefHttpRequest httpRequest = new DefHttpRequest(HttpMethod.POST, "/GetChannelInfo", staticHeadersMap, parseByte2HexStr(encryptData));
            httpClient.exec(httpRequest, ((ctx, response) -> {
                if (response.status().equals(HttpResponseStatus.OK)) {
                    String responseStr = aesDecode(response.content());
                    JSONObject root = JSONObject.parseObject(responseStr);
                    JSONArray channelList = root.getJSONArray("channelList");
                    channelMap.forEach((i,device)-> device.toOnline = false);

                    for (int i = 0;i < channelList.size();i++) {
                        JSONObject channel = channelList.getJSONObject(i);
                        String channelName = channel.getString("channelName");
                        int channelID = channel.getIntValue("channelID");
                        int inOrOut = channel.getIntValue("inOrOut");

                        Device device = channelMap.get(channelID);
                        if (device != null) {
                            TslEventAction action = new TslEventAction();
                            action.setDevId(device.devId);
                            Map<String,Object> values = new HashMap<>();
                            values.put("channelName",channelName);
                            values.put("inOrOut",inOrOut);
                            action.setOutputs(values);
                            listener.onEvent(action);
                            device.toOnline = true;
                        }
                    }

                    channelMap.forEach((i,device)-> {
                        if (!device.online.equals(device.toOnline)) {
                            updateDeviceStatus(device,device.toOnline);
                        }
                    });

                }
            }), true);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
    }

    @Override
    public DriverResult accessService(TslServiceAction serviceAction) {
        return DriverResult.SUCCESS;
    }

    @Override
    public DriverResult accessAttribute(TslAttrFrame tupleFrame) {
        DriverResult result = DriverResult.SUCCESS;
        if (tupleFrame.getOperation().equals(AttributeOperationEnum.WRITE)) {
            tupleFrame.getAttributeActions().forEach(tslAttributeAction -> {
               if (tslAttributeAction.getIdentifier().equals("action")) {
                   String value = tslAttributeAction.getValue().toString();
                   Device device = deviceIdMap.get(tslAttributeAction.getDevId());
                   if (device != null) {
                       ctrlParkChannel(device,value);
                   }
               }
            });
        }
        return result;
    }

    private void updateDeviceStatus(Device device,Boolean status)
    {
        device.online = status;
        TslEventAction action = new TslEventAction();
        action.setDevId(device.devId);
        action.setIdentifier(status ? CommonEventIdentifier.DEVICE_ONLINE : CommonEventIdentifier.DEVICE_OFFLINE);
        listener.onEvent(action);
    }
    
    public static void main(String[] args) throws Exception {
        LFParkAccess object = new LFParkAccess();
        object.exportDriverDescFile("");

        BootStrapManager.init(1,10);
        object.listener = event -> log.info("{}",event.toString());
        object.httpClient =  new HttpClient("http://192.168.25.100:9988/Parking/Handheld");
        object.userAccount = "admin";
        object.userPassword = "admin123";
        object.aesSecret = "cmVmb3JtZXJyZWZvcm1lcg==";
        object.init(null);
        Thread.sleep(1000*60);
        object.exit();
    }
}
